// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef UI_EVENTS_GESTURE_DETECTION_GESTURE_DETECTOR_H_
#define UI_EVENTS_GESTURE_DETECTION_GESTURE_DETECTOR_H_

#include <memory>

#include "base/memory/raw_ptr.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "ui/events/gesture_detection/gesture_detection_export.h"
#include "ui/events/gesture_detection/velocity_tracker_state.h"

namespace ui {

class DoubleTapListener;
class GestureListener;
class MotionEvent;

// Port of GestureDetector.java from Android
// * platform/frameworks/base/core/java/android/view/GestureDetector.java
// * Change-Id: Ib470735ec929b0b358fca4597e92dc81084e675f
// * Please update the Change-Id as upstream Android changes are pulled.
class GESTURE_DETECTION_EXPORT GestureDetector {
 public:
  struct GESTURE_DETECTION_EXPORT Config {
    Config();
    Config(const Config& other);
    ~Config();

    // Note: Most of these constants were taken directly from the default
    // (unscaled) versions found in Android's ViewConfiguration. Do not change
    // these default values without explicitly consulting an OWNER.
    //
    // Many of these default values get over-ridden by GestureConfiguration
    // parameters.  We have encountered exceptions to this behavior in certain
    // tests, see https://crbug.com/1294244.

    base::TimeDelta shortpress_timeout = base::Milliseconds(400);
    base::TimeDelta longpress_timeout = base::Milliseconds(500);
#ifdef OHOS_DRAG_DROP
    base::TimeDelta draglongpress_timeout = base::Milliseconds(1500);
#endif
#ifdef OHOS_AI
    base::TimeDelta createoverlay_timeout = base::Milliseconds(50);
#endif
    base::TimeDelta showpress_timeout = base::Milliseconds(180);
    base::TimeDelta double_tap_timeout = base::Milliseconds(300);

    // The minimum duration between the first tap's up event and the second
    // tap's down event for an interaction to be considered a double-tap.
    base::TimeDelta double_tap_min_time = base::Milliseconds(40);

    // Distance a stylus-contact can wander before a scroll will occur (in
    // dips).
    float stylus_slop = 12;

    // Distance a touch can wander before a scroll will occur (in dips).
    float touch_slop = 8;

    // Distance the first touch can wander before it is no longer considered a
    // double tap (in dips).
    float double_tap_slop = 100;

    // Minimum velocity to initiate a fling (in dips/second).
    float minimum_fling_velocity = 50;

    // Maximum velocity of an initiated fling (in dips/second).
    float maximum_fling_velocity = 8000;

    // Whether |OnSwipe| should be called after a secondary touch is released
    // while a logical swipe gesture is active. Defaults to false.
    bool swipe_enabled = false;

    // Minimum velocity to initiate a swipe (in dips/second).
    float minimum_swipe_velocity = 20;

    // Maximum angle of the swipe from its dominant component axis, between
    // (0, 45] degrees. The closer this is to 0, the closer the dominant
    // direction of the swipe must be to up, down left or right.
    float maximum_swipe_deviation_angle = 20;

    // Whether |OnTwoFingerTap| should be called for two finger tap
    // gestures. Defaults to false.
    bool two_finger_tap_enabled = false;

    // Maximum distance between pointers for a two finger tap.
    float two_finger_tap_max_separation = 300;

    // Maximum time the second pointer can be active for a two finger tap.
    base::TimeDelta two_finger_tap_timeout = base::Milliseconds(700);

    // Single tap count repetition length. Defaults to 1 (no repetition count).
    // Note that when double-tap detection is enabled, the single tap repeat
    // count will always be 1.
    int single_tap_repeat_interval = 1;

    // Whether a longpress should be generated immediately when a stylus button
    // is pressed, given that the longpress timeout is still active.
#if BUILDFLAG(IS_CHROMEOS_ASH)
    bool stylus_button_accelerated_longpress_enabled = true;
#else
    bool stylus_button_accelerated_longpress_enabled = false;
#endif

    // Whether a longpress should be generated immediately when a pointer is
    // deep-pressing, given that the longpress timeout is still active.
#if BUILDFLAG(IS_ANDROID)
    bool deep_press_accelerated_longpress_enabled = true;
#else
    bool deep_press_accelerated_longpress_enabled = false;
#endif

    VelocityTracker::Strategy velocity_tracker_strategy =
        VelocityTracker::Strategy::STRATEGY_DEFAULT;

    // If set the task runner to use for tasks generated by the
    // GestureDetector. If null the current sequence is used. This should be a
    // Browser UI thread task runner.
    scoped_refptr<base::SequencedTaskRunner> task_runner;
  };

  GestureDetector(const Config& config,
                  GestureListener* listener,
                  DoubleTapListener* optional_double_tap_listener);

  GestureDetector(const GestureDetector&) = delete;
  GestureDetector& operator=(const GestureDetector&) = delete;

  ~GestureDetector();

  bool OnTouchEvent(const MotionEvent& ev, bool should_process_double_tap);

  // Setting a valid |double_tap_listener| will enable double-tap detection,
  // wherein calls to |OnSimpleTapConfirmed| are delayed by the tap timeout.
  // Note: The listener must never be changed while |is_double_tapping| is true.
  void SetDoubleTapListener(DoubleTapListener* double_tap_listener);

  bool has_doubletap_listener() const { return !!double_tap_listener_; }

  bool is_double_tapping() const { return is_double_tapping_; }

  // Enables or disables gestures that require holding the finger steady for a
  // while (i.e. both short-press and long-press).
  void set_press_and_hold_enabled(bool enabled) {
    press_and_hold_enabled_ = enabled;
  }
  void set_showpress_enabled(bool enabled) { showpress_enabled_ = enabled; }
#ifdef OHOS_DRAG_DROP
  void set_draglongpress_enabled(bool enabled) {
    draglongpress_enabled_ = enabled;
  }
  void StopDragLongPressGesture();
#endif
#ifdef OHOS_AI
  void StopCreateOverlayGesture();
  void OnAITextSelected();
#endif
  // Returns the event storing the initial position of the pointer with given
  // pointer ID. This returns nullptr if the source event isn't
  // current_down_event_ or secondary_pointer_down_event_.
  const MotionEvent* GetSourcePointerDownEvent(
      const MotionEvent& current_down_event,
      const MotionEvent* secondary_pointer_down_event,
      const int pointer_id);

 private:
  void Init(const Config& config);
  void OnShowPressTimeout();
  void OnShortPressTimeout();
  void OnLongPressTimeout();
#ifdef OHOS_DRAG_DROP
  void OnDragLongPressTimeout();
  void Cancel(bool is_lost_focus);
  void CancelTaps(bool is_lost_focus);
  void ActivateLongPressKeepDragTimeout(const MotionEvent& ev);
#endif
#ifdef OHOS_AI
  void OnCreateOverlayTimeout();
#endif
  void OnTapTimeout();
  void ActivateShortPressGesture(const MotionEvent& ev);
  void ActivateLongPressGesture(const MotionEvent& ev);
  void Cancel();
  void CancelTaps();
  bool IsRepeatedTap(const MotionEvent& first_down,
                     const MotionEvent& first_up,
                     const MotionEvent& second_down,
                     bool should_process_double_tap) const;
  bool HandleSwipeIfNeeded(const MotionEvent& up, float vx, float vy);
  bool IsWithinSlopForTap(const MotionEvent& ev);

  class TimeoutGestureHandler;
  std::unique_ptr<TimeoutGestureHandler> timeout_handler_;
  const raw_ptr<GestureListener> listener_;
  raw_ptr<DoubleTapListener> double_tap_listener_;

  float stylus_slop_square_ = 0;
  float touch_slop_square_ = 0;
  float double_tap_touch_slop_square_ = 0;
  float double_tap_slop_square_ = 0;
  float two_finger_tap_distance_square_ = 0;
  float min_fling_velocity_ = 1;
  float max_fling_velocity_ = 1;
  float min_swipe_velocity_ = 0;
  float min_swipe_direction_component_ratio_ = 0;
  base::TimeDelta double_tap_timeout_;
  base::TimeDelta two_finger_tap_timeout_;
  base::TimeDelta double_tap_min_time_;

  bool still_down_ = false;
  bool defer_confirm_single_tap_ = false;
  bool all_pointers_within_slop_regions_ = false;
  bool always_in_bigger_tap_region_ = false;
  bool two_finger_tap_allowed_for_gesture_ = false;

  std::unique_ptr<MotionEvent> current_down_event_;
  std::unique_ptr<MotionEvent> previous_up_event_;
  std::unique_ptr<MotionEvent> secondary_pointer_down_event_;

  // True when the user is still touching for the second tap (down, move, and
  // up events). Can only be true if there is a double tap listener attached.
  bool is_double_tapping_ = false;

  // Whether the current ACTION_DOWN event meets the criteria for being a
  // repeated tap. Note that it will be considered a repeated tap only if the
  // corresponding ACTION_UP yields a valid tap and double-tap detection is
  // disabled.
  bool is_down_candidate_for_repeated_single_tap_ = false;

  // Stores the maximum number of pointers that have been down simultaneously
  // during the current touch sequence.
  int maximum_pointer_count_ = 0;

  // The number of repeated taps in the current sequence, i.e., for the initial
  // tap this is 0, for the first *repeated* tap 1, etc...
  int current_single_tap_repeat_count_ = 0;
  int single_tap_repeat_interval_ = 1;

  float last_focus_x_ = 0;
  float last_focus_y_ = 0;
  float down_focus_x_ = 0;
  float down_focus_y_ = 0;

  bool stylus_button_accelerated_longpress_enabled_ = false;
  bool deep_press_accelerated_longpress_enabled_ = false;
  bool press_and_hold_enabled_ = true;
  bool showpress_enabled_ = true;
  bool swipe_enabled_ = false;
  bool two_finger_tap_enabled_ = false;
#ifdef OHOS_DRAG_DROP
  bool draglongpress_enabled_ = true;
#endif
  // Determines speed during touch scrolling.
  VelocityTrackerState velocity_tracker_;
};

}  // namespace ui

#endif  // UI_EVENTS_GESTURE_DETECTION_GESTURE_DETECTOR_H_
